package com.linbit.drbd.md;

import com.linbit.linstor.LinStorException;
import java.security.SecureRandom;
import java.util.TreeSet;

public class GidGenerator
{
    // Values not usable as a random generation ID for DRBD, because those values
    // have a special meaning
    private static final Long[] RESERVED_GID_VALUES =
    {
        0L,
        4L
    };

    private static final int LOOP_GUARD = 100;

    // SecureRandom is multithreading-safe
    private static final SecureRandom RND = new SecureRandom();
    private static final TreeSet<Long> INVALID_GID_SET = new TreeSet<>();

    static
    {
        for (Long value : RESERVED_GID_VALUES)
        {
            INVALID_GID_SET.add(value);
        }
    }

    /**
     * Generates a new random DRBD generation identifier
     *
     * Values are generated with the DRBD Primary flag cleared. In the unlikely case where
     * a reserved value is generated (a value that causes some special behavior in DRBD),
     * generation of a different value is retried.
     * If the random number generator fails to provide a value that is not a reserved value
     * within the limited number of attempts made, a GidGeneratorException is thrown. This will
     * normally only happen if the generation of random numbers does not work properly on the
     * system and is extremely unlikely otherwise.
     *
     * @return String of hexadecimal characters that form a DRBD generation identifier
     */
    public static String generateRandomGid()
        throws GidGeneratorException
    {
        int counter = 0;
        boolean invalid = true;
        long gid = 0;
        while (invalid && counter < LOOP_GUARD)
        {
            ++counter;
            // Clear the lowest-order bit, which is used to indicate Primary/Secondary in DRBD
            gid = RND.nextLong() & ~1L;
            invalid = INVALID_GID_SET.contains(gid);
        }
        if (invalid)
        {
            String description = "The generation of a random generation identifier for DRBD metadata failed";
            throw new GidGeneratorException(
                description,
                description,
                "All values generated by the random number generator were reserved values that are unusable\n" +
                "as generation identifier, despite multiple attempts to generate a different number.\n" +
                "It is likely that random number generation does not work properly on this system.",
                "Check whether the generation of random numbers is supported and works correctly on this system.",
                "Generator used: " + RND.getClass().getSimpleName() + ", Attempts: " + LOOP_GUARD
            );
        }
        return String.format("%X", gid);
    }

    private GidGenerator()
    {
    }

    public static class GidGeneratorException extends LinStorException
    {
        GidGeneratorException(String message)
        {
            super(message);
        }

        GidGeneratorException(String message, Throwable cause)
        {
            super(message, cause);
        }

        GidGeneratorException(
            String message,
            String descriptionText,
            String causeText,
            String correctionText,
            String detailsText
        )
        {
            super(message, descriptionText, causeText, correctionText, descriptionText);
        }

        GidGeneratorException(
            String message,
            String descriptionText,
            String causeText,
            String correctionText,
            String detailsText,
            Throwable cause
        )
        {
            super(message, descriptionText, causeText, correctionText, descriptionText, cause);
        }
    }
}
